package cn.laixueit.resource.blog.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.net.URLDecoder;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.laixueit.po.TbBook;
import cn.laixueit.po.TbBookChapter;
import cn.laixueit.po.TbBookChapterContent;
import cn.laixueit.po.table.TbBookTableDef;
import cn.laixueit.resource.blog.mapper.TbBookMapper;
import cn.laixueit.resource.blog.service.TbBookChapterContentService;
import cn.laixueit.resource.blog.service.TbBookChapterService;
import cn.laixueit.resource.blog.service.TbBookService;
import cn.laixueit.vo.BookVo;
import com.mybatisflex.core.constant.SqlOperator;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.*;
import com.mybatisflex.core.update.UpdateChain;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.nio.charset.Charset;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

/**
 *
 */
@Service
@Slf4j
public class TbBookServiceImpl extends ServiceImpl<TbBookMapper, TbBook>
        implements TbBookService {

    @Resource
    private TbBookChapterService chapterService;
    @Resource
    private TbBookChapterContentService chapterContentService;

    @Override
    public Page<BookVo> bookPage(Integer page, Integer size, BookVo bookVo) {
        Optional.ofNullable(bookVo.getCategoryName()).ifPresent(x->bookVo.setCategoryName(URLDecoder.decode(x, Charset.defaultCharset())));
        Page<BookVo> pageInfo = new Page<>(page, size);
        SqlOperators sqlOperators = SqlOperators.of()
                .set(BookVo::getCreateTime, SqlOperator.GE)
                .set(BookVo::getName, SqlOperator.LIKE)
                .set(BookVo::getCreateUser, SqlOperator.LIKE)
                .set(BookVo::getCode, SqlOperator.LIKE);
        TbBook tbBook = BeanUtil.copyProperties(bookVo, TbBook.class);
        QueryWrapper wrapper = QueryWrapper.create(tbBook,sqlOperators);

        if(ObjUtil.isNotEmpty(bookVo.getYear())){
            LocalDate localDate = LocalDate.of(Integer.parseInt(bookVo.getYear()), 1, 1);
            DateTime endDate = DateUtil.endOfYear(Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant()));
            if(ObjUtil.isNotEmpty(bookVo.getMonth())){
                localDate = localDate.plusMonths(Long.valueOf(bookVo.getMonth())-1);
                endDate = DateUtil.endOfMonth(Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant()));
            }
            wrapper.between(TbBook::getCreateTime,localDate,endDate);
        }
        if(ObjUtil.isNotEmpty(bookVo.getHotSort()) && !bookVo.getHotSort()){
            wrapper.orderBy(TbBook::getHot,false);
        }
        wrapper.orderBy(TbBook::getSort,true)
                .orderBy(TbBook::getCreateTime,false)
                .orderBy(TbBook::getCode,false);
        Page<BookVo> bookPage = pageAs(pageInfo, wrapper, BookVo.class);
        return bookPage;
    }

    @Override
    public HashMap<String,Object> save(BookVo bookVo) {
        TbBook tbBook = BeanUtil.copyProperties(bookVo, TbBook.class);
        log.error("tbbook:{}", tbBook);
        if (!Optional.ofNullable(tbBook.getCode()).isPresent()) {
            String yyyyMMdd = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
            long count = count(queryChain().eq(TbBook::getCategoryId, tbBook.getCategoryId()).toQueryWrapper());
            tbBook.setCode(String.format(yyyyMMdd+"%03d", count+1));
        }
        if (!Optional.ofNullable(tbBook.getSort()).isPresent()) {
            long count = count(queryChain().eq(TbBook::getCategoryId, tbBook.getCategoryId()).toQueryWrapper());
            tbBook.setSort((int) (count + 1));
        }
        saveOrUpdate(tbBook);
        return new HashMap<String, Object>(){
            {put("id",tbBook.getId());}
        };
    }

    @Override
    public Boolean remove(String id,Boolean isDelete) {
        return UpdateChain.of(TbBook.class)
                .set(TbBook::getIsDelete, isDelete)
                .where(TbBook::getId).eq(id)
                .update();
    }

    @Override
    public BookVo findById(String id) {
//        TbBook tbBook = MaskManager.execWithoutMask(() -> getById(id));
        TbBook tbBook = getById(id);
        return BeanUtil.copyProperties(tbBook,BookVo.class);
    }

    @Resource
    private TbBookMapper bookMapper;

    @Override
    public BookVo findBookAndChapters(String id) {
        return bookMapper.selectOneWithRelationsByIdAs(id,BookVo.class);
    }

    @Override
    @Transactional
    public HashMap<String, Object> saveChapter(TbBookChapter chapter) {
        if(StrUtil.isBlank(chapter.getBookId())){
            Assert.notBlank(chapter.getBookId());
        }
        if(ObjUtil.isEmpty(chapter.getSort()) ){
            long count = chapterService.count(chapterService.queryChain().eq(TbBookChapter::getBookId,chapter.getBookId()).toQueryWrapper());
            chapter.setSort((int) (count+1));
        }
        // 同时生成空内容占位
        chapterService.saveOrUpdate(chapter);
        if(ObjUtil.isEmpty(chapter.getCreateTime())) {
            chapterContentService.save(TbBookChapterContent.builder().id(chapter.getId()).content("").build());
        }
        return new HashMap<String, Object>(){
            {put("id",chapter.getId());}
        };
    }

    @Override
    public List<TbBookChapter> listChapter(String bookId) {
        QueryWrapper wrapper = chapterService.queryChain()
                .eq(TbBookChapter::getBookId,bookId)
                .toQueryWrapper();
        List<TbBookChapter> chapters = chapterService.list(wrapper);
        return chapters;
    }

    @Override
    @Transactional
    public Boolean removeChapter(String chapterId) {
        return chapterService.removeById(chapterId) && chapterContentService.removeById(chapterId);
    }

    @Override
    public TbBookChapterContent findChapterContent(String chapterId) {
        TbBookChapterContent chapterContent = chapterContentService.getById(chapterId);
        return chapterContent;
    }

    @Override
    @Transactional
    public Boolean saveChapterContent(TbBookChapterContent chapterContent) {
        boolean state = chapterContentService.saveOrUpdate(chapterContent);
        if(!state){
            TbBookChapterContent content = chapterContentService.getById(chapterContent.getId());
            if(ObjUtil.isEmpty(content)){
                state = chapterContentService.save(TbBookChapterContent.builder().id(chapterContent.getId()).content(chapterContent.getContent()).build());
            }
        }
        return state;
    }

    @Override
    public List<TbBookChapter> findChapters(String bookId) {
        return this.findBookAndChapters(bookId).getChapterList();
    }

    @Override
    public List<HashMap> findBookCategoryNames( BookVo bookVo) {
        QueryWrapper wrapper = QueryWrapper.create().select(TbBookTableDef.TB_BOOK.CATEGORY_NAME.as("name")).select("count(1) as count").groupBy(TbBook::getCategoryName)
                .from(TbBookTableDef.TB_BOOK)
                .where(TbBookTableDef.TB_BOOK.NAME.like(bookVo.getName()).when(StrUtil.isNotBlank(bookVo.getName()))
                        .and(TbBookTableDef.TB_BOOK.CATEGORY_NAME.eq(bookVo.getCategoryName()).when(StrUtil.isNotBlank(bookVo.getCategoryName()))));
        if(ObjUtil.isNotEmpty(bookVo.getYear())){
            LocalDate localDate = LocalDate.of(Integer.parseInt(bookVo.getYear()), 1, 1);
            DateTime endDate = DateUtil.endOfYear(Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant()));
            if(ObjUtil.isNotEmpty(bookVo.getMonth())){
                localDate = localDate.plusMonths(Long.valueOf(bookVo.getMonth())-1);
                endDate = DateUtil.endOfMonth(Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant()));
            }
            wrapper.between(TbBook::getCreateTime,localDate,endDate);
        }
        List<HashMap> maps = listAs(wrapper, HashMap.class);
        return maps;
    }

    @Override
    public LinkedHashMap<String, Set<String>> findBookDate(BookVo bookVo) {
        List<TbBook> books = list(QueryWrapper.create().select(TbBookTableDef.TB_BOOK.CREATE_TIME).groupBy(TbBook::getCreateTime).orderBy(TbBook::getCreateTime,false)
                .from(TbBookTableDef.TB_BOOK)
                .where(TbBookTableDef.TB_BOOK.NAME.like(bookVo.getName()).when(StrUtil.isNotBlank(bookVo.getName()))
                        .and(TbBookTableDef.TB_BOOK.CATEGORY_NAME.eq(bookVo.getCategoryName()).when(StrUtil.isNotBlank(bookVo.getCategoryName()))))
        );
        Set<Integer> years = books.stream().map(TbBook::getCreateTime).map(x -> {
            LocalDate localDate = x.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
            return localDate.getYear();
        }).collect(Collectors.toSet());
        LinkedHashMap<String, Set<String>> map = new LinkedHashMap<>();
        years.stream().forEach(x->{
            map.put(x.toString(),new HashSet<>());
            books.stream().forEach(y->{
                LocalDate localDate = y.getCreateTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
                if (StrUtil.equals(x.toString(),localDate.getYear()+"")) {
                    map.get(x.toString()).add(localDate.getMonthValue()+"");
                }
            });
            Arrays.sort(map.get(x.toString()).toArray());
        });
        return map;
    }

    @Override
    public List<TbBook> randHotBook() {
        List<TbBook> books = list(QueryWrapper.create().where(TbBookTableDef.TB_BOOK.HOT.eq(1)).orderBy(QueryMethods.rand().desc()).limit(2));
        return books;
    }
}